#include "screen.h"

uint16_t SCREEN_W = 0;
uint16_t SCREEN_H = 0;

int32_t screenFb = 0;
bool isScreenFbAvailable = false;

struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;

uint32_t *screenFbp = (uint32_t *)MAP_FAILED;
bool isScreenFdpAvailable = false;

uint32_t screenPixelSize = 0;
Paint currentPaint;

bool Screen_Init() {
    // 打开帧缓冲设备文件
    screenFb = open(DEV_SCREEN, O_RDWR);
    if (screenFb < 0) {
        isScreenFbAvailable = false;
        return isScreenFbAvailable;
    }

    // 获取可变屏幕信息
    if (ioctl(screenFb, FBIOGET_VSCREENINFO, &vinfo) < 0) {
        Screen_Free();
        isScreenFbAvailable = false;
        return isScreenFbAvailable;
    }

    // 获取固定屏幕信息
    if (ioctl(screenFb, FBIOGET_FSCREENINFO, &finfo) < 0) {
        Screen_Free();
        isScreenFbAvailable = false;
        return isScreenFbAvailable;
    }

    SCREEN_W = vinfo.xres;
    SCREEN_H = vinfo.yres;
    uint32_t bpp = vinfo.bits_per_pixel;
    screenPixelSize = SCREEN_W * SCREEN_H * bpp / 8;

    // 显示信息
    printf("Screen resolution: %dx%d\n", SCREEN_W, SCREEN_H);
    printf("Bits per pixel: %d\n", bpp);

    currentPaint.strokeWidth = 1;
    currentPaint.color = WHITE;

    isScreenFbAvailable = true;
    return isScreenFbAvailable;
}

void Screen_Free() {
    if (isScreenFdpAvailable) {
        Screen_MapEnd();
    }

    if (isScreenFbAvailable) {
        close(screenFb);
        isScreenFbAvailable = false;
    }
}

bool Screen_MapStart() {
    if (!isScreenFbAvailable) {
        isScreenFdpAvailable = false;
        return isScreenFdpAvailable;
    }

    // 将帧缓冲设备映射到内存
    screenFbp = (uint32_t *)mmap(0, screenPixelSize, PROT_READ | PROT_WRITE, MAP_SHARED, screenFb, 0);
    if (screenFbp == MAP_FAILED) {
        isScreenFdpAvailable = false;
        return isScreenFdpAvailable;
    }

    isScreenFdpAvailable = true;
    return isScreenFdpAvailable;
}

void Screen_MapEnd() {
    if (!Screen_CanRW()) {
        return;
    }

    // 释放内存映射
    munmap(screenFbp, screenPixelSize);
    isScreenFdpAvailable = false;
}

bool Screen_CanRW() {
    return isScreenFbAvailable && isScreenFdpAvailable;
}

bool Screen_SetPaintStrokeWidth(uint8_t strokeWidth) {
    currentPaint.strokeWidth = strokeWidth;
}

bool Screen_SetPaintColor(ColorInt color) {
    currentPaint.color = color;
}

int32_t Screen_CalcIndex(uint16_t x, uint16_t y) {
    // (x + vinfo.xoffset) * (vinfo.bits_per_pixel / 8) + (y + vinfo.yoffset) * finfo.line_length;
    return y * SCREEN_W + x;
}

ColorInt Screen_ReadPixel(uint16_t x, uint16_t y) {
    return *(screenFbp + Screen_CalcIndex(x, y));
}

void Screen_DrawPixel(uint16_t x, uint16_t y) {
    Screen_DrawPixelWithColor(x, y, currentPaint.color);
}

void Screen_DrawPixelWithColor(uint16_t x, uint16_t y, ColorInt color) {
    *(screenFbp + Screen_CalcIndex(x, y)) = color;
}

void Screen_DrawPixel2(Point *p) {
    Screen_DrawPixel(p->x, p->y);
}

void Screen_DrawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
    Screen_DrawLineWithColor(x0, y0, x1, y1, currentPaint.color);
}

void Screen_DrawLineWithColor(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, ColorInt color) {
    int16_t dx = abs(x1 - x0);
    int16_t dy = abs(y1 - y0);
    int16_t sx = (x0 < x1) ? 1 : -1;
    int16_t sy = (y0 < y1) ? 1 : -1;
    int16_t err = dx - dy;

    uint8_t width = currentPaint.strokeWidth;
    int16_t halfWidth = width / 2;

    while (1) {
        if (dx > dy) {
            for (int16_t i = -halfWidth; i <= halfWidth; i++) {
                int16_t newY = y0 + i;
                if (newY >= 0 && newY < SCREEN_H && x0 >= 0 && x0 < SCREEN_W) {
                    Screen_DrawPixelWithColor(x0, newY, color);
                }
            }
        } else {
            for (int16_t i = -halfWidth; i <= halfWidth; i++) {
                int16_t newX = x0 + i;
                if (newX >= 0 && newX < SCREEN_W && y0 >= 0 && y0 < SCREEN_H) {
                    Screen_DrawPixelWithColor(newX, y0, color);
                }
            }
        }

        if (x0 == x1 && y0 == y1) {
            break;
        }
        int16_t e2 = 2 * err;
        if (e2 > -dy) {
            err = err - dy;
            x0 = x0 + sx;
        }
        if (e2 < dx) {
            err = err + dx;
            y0 = y0 + sy;
        }
    }
}

void Screen_DrawLine2(Line *line) {
    Screen_DrawLine(line->p0.x, line->p0.y, line->p1.x, line->p1.y);
}

void Screen_DrawLines(uint16_t lines[][2], uint16_t len) {
    size_t last = len - 1;
    for (size_t i = 0; i < last; i++) {
        Screen_DrawLine(lines[i][0], lines[i][1], lines[i + 1][0], lines[i + 1][1]);
    }
    Screen_DrawLine(lines[last][0], lines[last][1], lines[0][0], lines[0][1]);
}

// TODO: 线宽
void Screen_DrawHollowCircle(uint16_t xCenter, uint16_t yCenter, uint16_t radius) {
    Screen_DrawHollowCircleWithColor(xCenter, yCenter, radius, currentPaint.color);
}

void Screen_DrawHollowCircleWithColor(uint16_t xCenter, uint16_t yCenter, uint16_t radius, ColorInt color) {
    if (!Screen_CanRW()) {
        return;
    }

    int16_t x = 0, y = radius;
    int16_t d = 3 - 2 * radius;

    while (y >= x) {
        Screen_DrawPixelWithColor(xCenter + x, yCenter + y, color);
        Screen_DrawPixelWithColor(xCenter - x, yCenter + y, color);
        Screen_DrawPixelWithColor(xCenter + x, yCenter - y, color);
        Screen_DrawPixelWithColor(xCenter - x, yCenter - y, color);
        Screen_DrawPixelWithColor(xCenter + y, yCenter + x, color);
        Screen_DrawPixelWithColor(xCenter - y, yCenter + x, color);
        Screen_DrawPixelWithColor(xCenter + y, yCenter - x, color);
        Screen_DrawPixelWithColor(xCenter - y, yCenter - x, color);

        if (d < 0) {
            d += 4 * x + 6;
        } else {
            d += 4 * (x - y) + 10;
            y--;
        }
        x++;
    }
}

void Screen_DrawFilledCircle(uint16_t xCenter, uint16_t yCenter, uint16_t radius) {
    Screen_DrawFilledCircleWithColor(xCenter, yCenter, radius, currentPaint.color);
}

void Screen_DrawFilledCircleWithColor(uint16_t xCenter, uint16_t yCenter, uint16_t radius, ColorInt color) {
    if (!Screen_CanRW()) {
        return;
    }

    for (int16_t y = -radius; y <= radius; y++) {
        for (int16_t x = -radius; x <= radius; x++) {
            if (x * x + y * y <= radius * radius) {
                Screen_DrawPixelWithColor(xCenter + x, yCenter + y, color);
            }
        }
    }
}

void Screen_DrawFilledRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
    Screen_DrawFilledRectWithColor(x, y, w, h, currentPaint.color);
}

void Screen_DrawFilledRectWithColor(uint16_t x, uint16_t y, uint16_t w, uint16_t h, ColorInt color) {
    if (!Screen_CanRW()) {
        return;
    }

    uint8_t strokeWidth = currentPaint.strokeWidth;
    currentPaint.strokeWidth = 1;
    for (size_t y0 = y; y0 < y + h; y0++) {
        Screen_DrawLineWithColor(x, y0, w + x, y0, color);
    }
    currentPaint.strokeWidth = strokeWidth;
}

void Screen_DrawRoundedRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t radius) {
    Screen_DrawRoundedRectWithColor(x0, y0, w, h, radius, currentPaint.color);
}

void Screen_DrawRoundedRectWithColor(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t radius, ColorInt color) {
    if (!Screen_CanRW()) {
        return;
    }

    uint16_t x1 = x0 + w, y1 = y0 + h;

    for (size_t x = x0 + radius; x <= x1 - radius; x++) {
        for (size_t y = y0; y <= y1; y++) {
            Screen_DrawPixelWithColor(x, y, color);
            Screen_DrawPixelWithColor(x, y1 - (y - y0), color);
        }
    }

    for (size_t y = y0 + radius; y <= y1 - radius; y++) {
        for (size_t x = x0; x <= x1; x++) {
            Screen_DrawPixelWithColor(x, y, color);
        }
    }

    for (size_t i = 0; i < 4; ++i) {
        int8_t xOffset = (i % 2 == 0) ? -1 : 1;
        int8_t yOffset = (i < 2) ? -1 : 1;
        int16_t xCenter = (i % 2 == 0) ? x0 + radius : x1 - radius;
        int16_t yCenter = (i < 2) ? y0 + radius : y1 - radius;

        for (size_t x = 0; x <= radius; ++x) {
            for (size_t y = 0; y <= radius; ++y) {
                if (x * x + y * y <= radius * radius) {
                    Screen_DrawPixelWithColor(xCenter + xOffset * x, yCenter + yOffset * y, color);
                }
            }
        }
    }
}

void Screen_Fill() {
    Screen_DrawFilledRect(0, 0, SCREEN_W - 1, SCREEN_H);
}

void Screen_DrawChar(uint16_t w, uint16_t h, uint8_t word[][w / 8], uint16_t x, uint16_t y, ColorInt color) {
    for (size_t i = 0; i < h; i++) {
        for (size_t j = 0; j < w / 8; j++) {
            for (int8_t k = 7; k >= 0; k--) {
                if (word[i][j] & (1 << k)) {
                    Screen_DrawPixelWithColor(j * 8 + (7 - k) + x, i + y, color);
                }
            }
        }
    }
}

void __InternalDrawNumber(int32_t num, uint16_t x, uint16_t y, int8_t depth, ColorInt color) {
    if (num == 0) {
        return;
    }

    Screen_DrawChar(ASCII_CHAR_W, ASCII_CHAR_H, CHAR_NUMBER[num % 10], x + 32 * depth, y, color);
    __InternalDrawNumber(num / 10, x, y, depth - 1, color);
}

void Screen_DrawNumber(int32_t num, uint16_t x, uint16_t y, ColorInt color) {
    if (num <= 0) {
        Screen_DrawChar(ASCII_CHAR_W, ASCII_CHAR_H, CHAR_NUMBER[0], x, y, color);
        return;
    }

    __InternalDrawNumber(num, x, y, (int)(log10(num)), color);

    // uint8_t depth = (uint8_t)(log10(num / 10) + 1);
    // Screen_DrawWord(CHAR_W, CHAR_H, CHAR_NUMBER[num % 10], x + CHAR_W * depth, y);
    // Screen_DrawNumber(num / 10, x, y);
}

void __InternalDrawTimePart(uint8_t num, uint16_t *x, uint16_t y, ColorInt color) {
    if (num < 10) {
        Screen_DrawNumber(0, *x, y, color);
        *x += ASCII_CHAR_W;
    }
    Screen_DrawNumber(num, *x, y, color);
    *x += ASCII_CHAR_W * (num < 10 ? 1 : 2);
}

void __InternalDrawTime(uint32_t seconds, uint16_t x, uint16_t y, ColorInt color) {
    Time time = Time_Make(seconds);
    uint8_t hour = time.hour;
    uint8_t minute = time.minute;
    uint8_t second = time.second;

    __InternalDrawTimePart(hour, &x, y, color);
    Screen_DrawChar(ASCII_CHAR_W, ASCII_CHAR_H, CHAR_COLON, x, y, color);
    x += ASCII_CHAR_W;

    __InternalDrawTimePart(minute, &x, y, color);
    Screen_DrawChar(ASCII_CHAR_W, ASCII_CHAR_H, CHAR_COLON, x, y, color);
    x += ASCII_CHAR_W;

    __InternalDrawTimePart(second, &x, y, color);
}

void Screen_DrawTime(uint32_t seconds, uint16_t x, uint16_t y, ColorInt color, TimeMode mode) {
    ColorInt *savedData = (ColorInt *)malloc(8 * ASCII_CHAR_W * ASCII_CHAR_H * sizeof(ColorInt));

    int32_t i = seconds;
    while (seconds >= 0) {
        if (i == seconds) {
            Screen_SaveRect(x, y, 8 * ASCII_CHAR_W, ASCII_CHAR_H, savedData);
        } else {
            Screen_RestoreRect(x, y, 8 * ASCII_CHAR_W, ASCII_CHAR_H, savedData);
        }

        __InternalDrawTime(i, x, y, color);
        if (mode == NORMAL) {
            i++;
        } else {
            i--;
        }

        Delay(1000);
    }

    free(savedData);
}

void Screen_ShowBMP(BMP bmp, uint16_t x, uint16_t y) {
    uint32_t width = bmp.width;
    uint32_t height = bmp.height;

    for (size_t y0 = y, i = 0; y0 < SCREEN_H && y0 < y + bmp.height; y0++) {
        for (size_t x0 = x; x0 < SCREEN_W && x0 < x + bmp.width; x0++, i++) {
            if (bmp.entry[i].alpha == 0) {
                continue;
            }

            ColorInt color = Color_ARGB(bmp.entry[i].alpha, bmp.entry[i].red, bmp.entry[i].green, bmp.entry[i].blue);
            Screen_DrawPixelWithColor(x0, y0, color);
        }
    }
}

void Screen_ShowBMPByFile(const char *fileName, uint16_t x, uint16_t y) {
    BMP bmp = readBMP(fileName);
    if (bmp.entry != NULL) {
        Screen_ShowBMP(bmp, x, y);
        free(bmp.entry);
    }
}

void Screen_ShowBMPWithMode(BMP bmp, BMPShowMode mode) {
    uint32_t width = bmp.width;
    uint32_t height = bmp.height;

    switch (mode) {
        case LEFT_IN:
            for (int16_t x = 0; x < width; x++) {
                for (int16_t y = 0; y < height; y++) {
                    uint32_t i = width * y + x;

                    ColorInt color = Color_RGB(bmp.entry[i].red, bmp.entry[i].green, bmp.entry[i].blue);
                    Screen_DrawPixelWithColor(x, y, color);
                }
                usleep(100);
            }
            break;
        case RIGHT_IN:
            for (int16_t x = width - 1; x >= 0; x--) {
                for (int16_t y = 0; y < height; y++) {
                    uint32_t i = width * y + x;

                    ColorInt color = Color_RGB(bmp.entry[i].red, bmp.entry[i].green, bmp.entry[i].blue);
                    Screen_DrawPixelWithColor(x, y, color);
                }
                usleep(100);
            }
            break;
        case BOTTOM_IN:
            for (int16_t y = height - 1; y >= 0; y--) {
                for (int16_t x = 0; x < width; x++) {
                    uint32_t i = width * y + x;

                    ColorInt color = Color_RGB(bmp.entry[i].red, bmp.entry[i].green, bmp.entry[i].blue);
                    Screen_DrawPixelWithColor(x, y, color);
                }
                usleep(100);
            }
            break;
        case TOP_IN:
        default:
            for (int16_t y = 0; y < height; y++) {
                for (int16_t x = 0; x < width; x++) {
                    uint32_t i = width * y + x;

                    ColorInt color = Color_RGB(bmp.entry[i].red, bmp.entry[i].green, bmp.entry[i].blue);
                    Screen_DrawPixelWithColor(x, y, color);
                }
                usleep(100);
            }
            break;
    }
}

void Screen_ShowBMPWithModeByFile(const char *fileName, BMPShowMode mode) {
    BMP bmp = readBMP(fileName);
    if (bmp.entry != NULL) {
        Screen_ShowBMPWithMode(bmp, mode);
        free(bmp.entry);
    }
}

void Screen_SaveRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, ColorInt *savedData) {
    if (savedData == NULL) {
        return;
    }

    for (size_t y0 = y, i = 0; y0 < y + h; y0++) {
        for (size_t x0 = x; x0 < x + w; x0++, i++) {
            savedData[i] = Screen_ReadPixel(x0, y0);
        }
    }
}

void Screen_RestoreRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, ColorInt *savedData) {
    if (savedData == NULL) {
        return;
    }

    for (size_t y0 = y, i = 0; y0 < y + h; y0++) {
        for (size_t x0 = x; x0 < x + w; x0++, i++) {
            Screen_DrawPixelWithColor(x0, y0, savedData[i]);
        }
    }
}
